const defineDescriptor = (src, dest, name) => {
    if (!dest.hasOwnProperty(name)) {
      const descriptor = Object.getOwnPropertyDescriptor(src, name);
      Object.defineProperty(dest, name, descriptor);
    }
  };
  
  const merge = objs => {
    const res = {};
    objs.forEach(obj => {
      obj &&
        Object.getOwnPropertyNames(obj).forEach(name =>
          defineDescriptor(obj, res, name)
        );
    });
    return res;
  };
  
  const buildFromProps = (obj, props) => {
    const res = {};
    props.forEach(prop => defineDescriptor(obj, res, prop));
    return res;
  };
  
  export default {
    props: {
      template: String,
      parent: Object,
      templateProps: {
        type: Object,
        default: () => ({})
      }
    },
    render(h) {
      if (this.template) {
        const parent = this.parent || this.$parent
        const {
          $data: parentData = {},
          $props: parentProps = {},
          $options: parentOptions = {}
        } = parent;
        const {
          components: parentComponents = {},
          computed: parentComputed = {},
          methods: parentMethods = {}
        } = parentOptions;
        const {
          $data = {},
          $props = {},
          $options: { methods = {}, computed = {}, components = {} } = {}
        } = this;
        const passthrough = {
          $data: {},
          $props: {},
          $options: {},
          components: {},
          computed: {},
          methods: {}
        };
  
        //build new objects by removing keys if already exists (e.g. created by mixins)
        Object.keys(parentData).forEach(e => {
          if (typeof $data[e] === "undefined")
            passthrough.$data[e] = parentData[e];
        });
        Object.keys(parentProps).forEach(e => {
          if (typeof $props[e] === "undefined")
            passthrough.$props[e] = parentProps[e];
        });
        Object.keys(parentMethods).forEach(e => {
          if (typeof methods[e] === "undefined")
            passthrough.methods[e] = parentMethods[e];
        });
        Object.keys(parentComputed).forEach(e => {
          if (typeof computed[e] === "undefined")
            passthrough.computed[e] = parentComputed[e];
        });
        Object.keys(parentComponents).forEach(e => {
          if (typeof components[e] === "undefined")
            passthrough.components[e] = parentComponents[e];
        });
  
        const methodKeys = Object.keys(passthrough.methods || {});
        const dataKeys = Object.keys(passthrough.$data || {});
        const propKeys = Object.keys(passthrough.$props || {});
        const templatePropKeys = Object.keys(this.templateProps);
        const allKeys = dataKeys.concat(propKeys).concat(methodKeys).concat(templatePropKeys);
        const methodsFromProps = buildFromProps(parent, methodKeys);
        const finalProps = merge([
          passthrough.$data,
          passthrough.$props,
          methodsFromProps,
          this.templateProps
        ]);
        const provide = this.$parent._provided;
         
  
        const dynamic = {
          template: this.template || "<div></div>",
          props: allKeys,
          computed: passthrough.computed,
          components: passthrough.components,
          provide: provide
        };
  
        return h(dynamic, { props: finalProps });
      }
    }
  };
  